package cn.myapps.msg.sender;

import cn.hutool.json.JSONUtil;
import cn.myapps.msg.PushControl;
import cn.myapps.msg.PushData;
import cn.myapps.msg.enums.MsgStatusEnum;
import cn.myapps.msg.maker.WxMpTemplateMsgMaker;
import cn.myapps.msg.vo.MsgCommon;
import cn.myapps.msg.vo.SendResult;
import cn.myapps.msg.wx.entity.MsgMpTemplate;
import cn.myapps.msg.wx.entity.WxAccount;
import cn.myapps.msg.wx.vo.DefaultWxMpTemplate;
import cn.myapps.msg.wx.vo.WxMpTemplate;
import cn.myapps.msg.wx.service.IMsgMpTemplateService;
import cn.myapps.msg.wx.service.IWxAccountService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.apache.commons.collections.MapUtils;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.util.EntityUtils;
import org.jeecg.common.util.SpringContextUtils;

import java.io.IOException;
import java.util.concurrent.Future;

/**
 * @Description: 微信公众号模板消息发送器
 * @Author: nicholas
 * @Date:   2020-06-10
 * @Version: V1.0
 */
@Slf4j
public class WxMpTemplateMsgSender implements IMsgSender {
    private WxMpTemplateMsgMaker wxMpTemplateMsgMaker;
    private IWxAccountService wxAccountService;
    private IMsgMpTemplateService msgMpTemplateService;

    public WxMpTemplateMsgSender() {
        wxMpTemplateMsgMaker = new WxMpTemplateMsgMaker();
        wxAccountService = SpringContextUtils.getBean(IWxAccountService.class);
        msgMpTemplateService = SpringContextUtils.getBean(IMsgMpTemplateService.class);
    }

    @Override
    public SendResult send(MsgCommon msg) {
        throw new UnsupportedOperationException("不支持一般发送模式");
    }

    /**
     * 发送模板消息
     *
     * @param msg
     * @return
     */
    @Override
    public SendResult sendByTemplate(MsgCommon msg){
        wxMpTemplateMsgMaker.prepare(new DefaultWxMpTemplate(msg.getTemplateCode()));
        MsgMpTemplate msgMp = wxMpTemplateMsgMaker.makeMsg(msg);

        return doSend(msgMp, msg);
    }

    public SendResult doSend(MsgMpTemplate msgMp, MsgCommon msg){
        SendResult sendResult = new SendResult();
        try {
            WxMpTemplateMessage wxMessageTemplate = wxMpTemplateMsgMaker.toWxMpTemplateMessage(msgMp);

            if (PushControl.dryRun) {
                sendResult.setSuccess(true);
                return sendResult;
            } else {
                WxMpService wxMpService = getWxMpService(msg);
                String msgId = wxMpService.getTemplateMsgService().sendTemplateMsg(wxMessageTemplate);
                msgMp.setMsgId(msgId);
            }

            msgMp.setMsgStatus(MsgStatusEnum.SENED.getCode());
        } catch (Exception e) {
            log.error("发送微信模板消息异常", e);

            sendResult.setSuccess(false);
            sendResult.setInfo(e.getMessage());

            msgMp.setMsgStatus(MsgStatusEnum.ERROR.getCode());

            return sendResult;
        }

        msgMpTemplateService.save(msgMp);

        sendResult.setSuccess(true);

        return sendResult;
    }

    @Override
    public SendResult asyncSend(MsgCommon msg) {
        SendResult sendResult = new SendResult();

        CloseableHttpAsyncClient client = null;
        try {
            if (PushControl.dryRun) {
                // 已成功+1
                PushData.increaseSuccess();
                // 保存发送成功
                PushData.sendSuccessList.add(msg);
                // 总进度条
                sendResult.setSuccess(true);
                return sendResult;
            } else {
                MsgMpTemplate msgMp = wxMpTemplateMsgMaker.makeMsg(msg);
                WxMpTemplateMessage wxMessageTemplate = wxMpTemplateMsgMaker.toWxMpTemplateMessage(msgMp);

                WxMpService wxMpService = getWxMpService(msg);

                String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + wxMpService.getAccessToken();
                HttpPost httpPost = new HttpPost(url);
                StringEntity entity = new StringEntity(wxMessageTemplate.toJson(), Consts.UTF_8);
                httpPost.setEntity(entity);
                if (wxMpService.getRequestHttp().getRequestHttpProxy() != null) {
                    RequestConfig config = RequestConfig.custom().setProxy((HttpHost) wxMpService.getRequestHttp().getRequestHttpProxy()).build();
                    httpPost.setConfig(config);
                }
                client = getCloseableHttpAsyncClient();

                Future<HttpResponse> httpResponseFuture = client.execute(httpPost, new CallBack(msg));
            }
        } catch (Exception e) {
            // 总发送失败+1
            PushData.increaseFail();

            // 保存发送失败
            PushData.sendFailList.add(msg);

            // 失败异常信息输出控制台
            log.error("发送失败:" + e.toString() + ";msgData:" + JSONUtil.toJsonPrettyStr(msg));

            sendResult.setSuccess(false);
            sendResult.setInfo(e.getMessage());
            log.error("发送微信模板消息异常", e);

            return sendResult;
        } finally {
            try {
                if (client != null) {
                    client.close();
                }
            } catch (IOException e) {
                log.error("HttpClient close error", e);
            }
        }

        return sendResult;
    }

    /**
     * 微信公众号配置
     *
     * @return WxMpConfigStorage
     */
    private WxMpDefaultConfigImpl wxMpConfigStorage(String accountName) {
        WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();

        WxAccount account = wxAccountService.findByAccountName(accountName);

        configStorage.setAppId(account.getAppId());
        configStorage.setSecret(account.getAppSecret());
        configStorage.setToken(account.getToken());
        configStorage.setAesKey(account.getAesKey());

//        if (App.config.isMpUseProxy()) {
//            configStorage.setHttpProxyHost(App.config.getMpProxyHost());
//            configStorage.setHttpProxyPort(Integer.parseInt(App.config.getMpProxyPort()));
//            configStorage.setHttpProxyUsername(App.config.getMpProxyUserName());
//            configStorage.setHttpProxyPassword(App.config.getMpProxyPassword());
//        }

        DefaultApacheHttpClientBuilder clientBuilder = DefaultApacheHttpClientBuilder.get();
        //从连接池获取链接的超时时间(单位ms)
        clientBuilder.setConnectionRequestTimeout(10000);
        //建立链接的超时时间(单位ms)
        clientBuilder.setConnectionTimeout(5000);
        //连接池socket超时时间(单位ms)
        clientBuilder.setSoTimeout(5000);
        //空闲链接的超时时间(单位ms)
        clientBuilder.setIdleConnTimeout(60000);
        //空闲链接的检测周期(单位ms)
        clientBuilder.setCheckWaitTime(60000);
        //每路最大连接数
        clientBuilder.setMaxConnPerHost(account.getMaxThreadPool() * 2);
        //连接池最大连接数
        clientBuilder.setMaxTotalConn(account.getMaxThreadPool() * 2);
        //HttpClient请求时使用的User Agent
//        clientBuilder.setUserAgent(..)
        configStorage.setApacheHttpClientBuilder(clientBuilder);
        return configStorage;
    }

    /**
     * 获取微信公众号工具服务
     *
     * @return WxMpService
     */
    public WxMpService getWxMpService(MsgCommon msg) {
        WxMpService wxMpService = new WxMpServiceImpl();
        //String appId = MapUtils.getString(msg.getOtherParams(), "appId");
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage(msg.getFrom()));

        return wxMpService;
    }

    public CloseableHttpAsyncClient getCloseableHttpAsyncClient() throws IOReactorException {
        CloseableHttpAsyncClient closeableHttpAsyncClient = null;

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(-1)
                .setSocketTimeout(-1)
                .setConnectionRequestTimeout(-1)
                .build();

        //配置io线程
        IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
                setIoThreadCount(Runtime.getRuntime().availableProcessors())
                .setSoKeepAlive(true).setConnectTimeout(-1).setSoTimeout(-1)
                .build();
        //设置连接池大小
        ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
        PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
        //最大连接数
        connManager.setMaxTotal(5000);
        //per route最大连接数
        connManager.setDefaultMaxPerRoute(5000);

        closeableHttpAsyncClient = HttpAsyncClients.custom().
                setConnectionManager(connManager)
                .setDefaultRequestConfig(requestConfig)
                .build();

        closeableHttpAsyncClient.start();

        return closeableHttpAsyncClient;
    }

    static class CallBack implements FutureCallback<HttpResponse> {

        MsgCommon msg;

        CallBack(MsgCommon msg) {
            this.msg = msg;
        }

        @Override
        public void completed(HttpResponse httpResponse) {
            try {
                String response = EntityUtils.toString(httpResponse.getEntity(), Consts.UTF_8);
                if (response.isEmpty()) {
                    // 总发送失败+1
                    PushData.increaseFail();
                    // 保存发送失败
                    PushData.sendFailList.add(msg);

                    // 失败异常信息输出控制台
                    log.error("发送失败:" + WxError.builder().errorCode(9999).errorMsg("无响应内容").build() + ";msgData:" + JSONUtil.toJsonPrettyStr(msg));
                } else {
                    WxError error = WxError.fromJson(response);
                    if (error.getErrorCode() != 0) {
                        // 总发送失败+1
                        PushData.increaseFail();

                        // 保存发送失败
                        PushData.sendFailList.add(msg);

                        // 失败异常信息输出控制台
                        log.error("发送失败:" + error + ";msgData:" + JSONUtil.toJsonPrettyStr(msg));
                    } else {
                        // 已成功+1
                        PushData.increaseSuccess();
                        // 保存发送成功
                        PushData.sendSuccessList.add(msg);
                    }
                }
            } catch (Exception e) {
                log.error("处理异常", e);
            }
        }

        @Override
        public void failed(Exception e) {
            // 总发送失败+1
            PushData.increaseFail();
            // 保存发送失败
            PushData.sendFailList.add(msg);

            // 失败异常信息输出控制台
            log.error("发送失败:" + e.toString() + ";msgData:" + JSONUtil.toJsonPrettyStr(msg));
        }

        @Override
        public void cancelled() {
            PushData.TO_SEND_COUNT.getAndDecrement();
        }
    }
}
